package mars.io

import mars.io.formatters.Formatter
import mars.io.formatters.FormatException

import scala.io.Source
import scala.collection.mutable.HashMap
import scala.collection.Iterable

import java.io.File
import java.io.PrintWriter
import java.io.IOException

/**
 * The IO object defines functions to create instances of the IO class
 */
object IO {

    private val fileMap = new HashMap[File, IO[_]]()

    /**
     * Creates an instance of the IO class with the file at the given path
     * as parameter
     *
     * There will be only one instance of the IO class per object
     *
     * @param 	path
     * 			A string representing the path to the file which the instance will
     * 			use to read from/write to
     * @param	formatter
     * 			The formatter that will convert the objects to a string and vice versa
     * @return  An instance of the IO class with the file at the given path as parameter
     * 			| return IO.getInstance(new File(path))
     * @throws 	IOException
     * 			If the given path points to a directory
     * 			| (new File(path)).isDirectory() == true
     * @throws  IOException
     * 			If there is no read or write access to the file at the given path
     * 			| ((new File(path)).canRead() == true || (new File(path)).canWrite() == true)
     */
    def getInstance[T](path: String, formatter: Formatter[T]): IO[T] = getInstance(new File(path), formatter)

    /**
     * Creates an instance of the IO class with the given file as parameter
     *
     * There will be only one instance of the IO class per object
     *
     * @param 	file
     * 			The file which the instance will use to read from/write to
     * @param	formatter
     * 			The formatter that will convert the objects to a string and vice versa
     * @return  An instance of the IO class with the file at the given path as parameter
     * 			| return new IO(file)
     * @throws 	IOException
     * 			If the given file points to a directory
     * 			| file.isDirectory() == true
     * @throws  IOException
     * 			If there is no read or write access to the given file
     * 			| (file.canRead() == true || file.canWrite() == true)
     */
    def getInstance[T](file: File, formatter: Formatter[T] ): IO[T] = {
        return fileMap.get(file.getAbsoluteFile()) match {
            case io: IO[T] => io
            case None => {
                val io: IO[T] = new IO[T](file, formatter)
                fileMap.put(file, io)
                io
            }
        }
    }

}

/**
 * This class provides some functions to easily save objects to a text file
 * and load objects from a text file
 *
 */
class IO[T] private (private val file: File, private val formatter: Formatter[T]) {

    checkFile()

    //Checks if it is possible to read from/write to the file and that is is not a directory
    private def checkFile() = {
        if (!file.canRead() || !file.canWrite()) {
            throw new IOException("No read or write right for file [" + file.getAbsolutePath() + "]")
        }
        else if (file.isDirectory()) {
            throw new IOException("The given file is a directory [" + file.getAbsolutePath() + "]")
        }
    }

    /**
     * Saves an object to a text file
     *
     * @param 	obj
     * 			The object
     * @effect	All the given objects will be saved in the file
     * 			| (load(formatter) == obj) == true
     */
    def save(obj: T) = {
        val out = new PrintWriter(file)
        out.print(formatter.format(obj))
    }

    /**
     * Loads an object from a text file
     *
     * @param 	formatter
     * 			The formatter that will convert the text to an object
     * @return	The object saved in the file
     * @throws 	FormatException
     * 			If the given file is empty
     * 			| (Source.fromFile(file).mkString.isEmpty) == true
     * @throws 	FormatException
     * 			If the text in the file is not properly formatted
     * 			| formatter.validFormat(Source.frilFile(file).mkString) == false
     */
    def load(): T = {
        val source: Source = Source.fromFile(file)
        val text: String = source.mkString
        if (text.isEmpty) {
            throw new FormatException("The file is empty [" + file.getAbsolutePath() + "]")
        }
        if (!formatter.validFormat(text)) {
            throw new FormatException("The given text is not properly formatted")
        }
        return formatter.fromString(text)
    }
}